package top.yangbuyi.config.shiro;

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.IRedisManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.web.filter.DelegatingFilterProxy;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import top.yangbuyi.config.redis.KickoutSessionControlFilter;

import javax.servlet.Filter;
import java.util.*;


/**
 * @description: 杨不易网站:www.yangbuyi.top
 * @program: yangbuyiopenHis2020
 * @ClassName: ShiroAutoConfiguration
 * @create: 2020-08-28 00:37
 * @author: yangbuyi
 * @since： JDK1.8
 * @ShiroAutoConfiguration: Shiro自动配置$
 **/

@Configuration
@EnableConfigurationProperties(value = {ShiroProperties.class, RedisProperties.class})
@Slf4j
public class ShiroAutoConfiguration {

	  private ShiroProperties shiroProperties;

	  private RedisProperties redisProperties;

	  public static final String SHIRO_FILTER_NAME = "shiroFilter";

	  public ShiroAutoConfiguration(ShiroProperties shiroProperties, RedisProperties redisProperties) {
			this.shiroProperties = shiroProperties;
			this.redisProperties = redisProperties;
	  }

	  /**
	   * @Description: 杨不易个人网址:http://yangbuyi.top
	   * 功能描述: 创建凭证匹配器
	   * @Author: TeouBle
	   * @Date: 2020/8/28 0:39
	   */
	  @Bean
	  public HashedCredentialsMatcher getHashedCredentialsMatcher() {
			HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
			//注入散列算法名
			matcher.setHashAlgorithmName(shiroProperties.getHashAlgorithmName());
			//注入散列次数
			matcher.setHashIterations(shiroProperties.getHashIterations());
			log.info("注入散列算法名" + shiroProperties.getHashAlgorithmName());
			log.info("注入散列次数" + shiroProperties.getHashIterations());
			return matcher;
	  }

	  /**
	   * @Description: 杨不易个人网址:http://yangbuyi.top
	   * 功能描述: 创建自定义Realm 并且注入凭证匹配器
	   * @Author: TeouBle
	   * @Date: 2020/8/28 0:40
	   */
	  @Bean
	  @ConditionalOnClass(value = {UserRealm.class})
	  public UserRealm getUserRealm(HashedCredentialsMatcher matcher) {
			UserRealm userRealm = new UserRealm();
			//注入凭证匹配器
			userRealm.setCredentialsMatcher(matcher);
			return userRealm;
	  }

	  /**
	   * @Description: 杨不易个人网址:http://yangbuyi.top
	   * 功能描述: 创建安全管理器 这里重写了 DefaultWebSessionManager getSessionID 自定义生成token  --- 可改JWT
	   * @Author: TeouBle
	   * @Date: 2020/8/28 0:42
	   */
	  @Bean
	  @ConditionalOnClass(value = DefaultWebSecurityManager.class)
	  public DefaultWebSecurityManager getSecurityManager(DefaultWebSessionManager defaultWebSessionManager, UserRealm userRealm) {
			DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
			//注入realm
			securityManager.setRealm(userRealm);
//			securityManager.setCacheManager(cacheManager());
			// 注入安全管理器
			securityManager.setSessionManager(defaultWebSessionManager);
			return securityManager;
	  }


	  /**
	   * @Description: 杨不易个人网址:http://yangbuyi.top
	   * 功能描述: 声明过滤器 --- Shiro 的Web过滤器 id必须和web.xml里面的shiroFilter的 targetBeanName的值一样
	   * @Author: TeouBle
	   * @Date: 2020/8/28 0:47
	   */
	  @Bean(value = SHIRO_FILTER_NAME)
	  public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager,DefaultWebSessionManager defaultWebSessionManager) {
			ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
			//注入安全管理器
			bean.setSecurityManager(securityManager);
			// 如果用户访问需要认证的页面 而当前用户没有进项认证时跳转的页面
//			bean.setLoginUrl(this.shiroProperties.getLoginUrl());

			//处理用户未认证访问要认证的地址的跳转问题   默认是跳转到shiroProperties.getLoginUrl()现在改成以json串形式返回
			LinkedHashMap<String, Filter> filters = new LinkedHashMap<String, Filter>();
			// 自定义认证
			filters.put("authc", new ShiroLoginFilter());
			//限制同一帐号同时在线的个数。
//			filters.put("kickout", kickoutSessionControlFilter(defaultWebSessionManager));
			// 注入自定义认证过滤器
			bean.setFilters(filters);

			// 注入不拦截路径 和 拦截路径
			LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
			// 配置不拦击的路径
			String[] anonUrls = shiroProperties.getAnonUrls();
			if (anonUrls != null && anonUrls.length > 0) {
				  for (String anonUrl : anonUrls) {
						map.put(anonUrl, "anon");
				  }
			}
			//配置拦截的路径
			String[] authcUrls = this.shiroProperties.getAuthcUrls();
			if (authcUrls != null && authcUrls.length > 0) {
				  for (String authcUrl : authcUrls) {
						map.put(authcUrl, "authc");
				  }
			}
			// 注入过滤拦截规则
			bean.setFilterChainDefinitionMap(map);
			return bean;
	  }

	 /**
	 * @Description: 杨不易个人网址:http://yangbuyi.top
	 * 功能描述: 注册DelegatingFilterProxy 代理过滤器 dispacherservlet
	 * @Param:
	 * @return:
	 * @Author: TeouBle
	 * @Date: 2020/8/28 10:06
	 */
	  @Bean
	  public FilterRegistrationBean<DelegatingFilterProxy> registDelegatingFilterProxy() {
			//创建注册器
			FilterRegistrationBean<DelegatingFilterProxy> bean = new FilterRegistrationBean<>();
			//创建过滤器
			DelegatingFilterProxy proxy = new DelegatingFilterProxy();
			//注入过滤器
			bean.setFilter(proxy);
			proxy.setTargetFilterLifecycle(true);
			proxy.setTargetBeanName(SHIRO_FILTER_NAME);
			Collection<String> servleNames = new ArrayList<>();
			servleNames.add(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
			bean.setServletNames(servleNames);
			return bean;
	  }


	  /**
	  * @Description: 杨不易个人网址:http://yangbuyi.top
	  * 功能描述: sessionManager 里面可以决定sessionDAO
	  * @deprecated: defaultWebSessionManager来源于top.yangbuyi.config.shiro.TokenWebSessionManager
	  * @Author: TeouBle
	  * @Date: 2020/8/28 10:08
	  */
	  @Bean
	  public TokenWebSessionManager defaultWebSessionManager(RedisSessionDAO redisSessionDao) {
			TokenWebSessionManager defaultWebSessionManager = new TokenWebSessionManager();
			defaultWebSessionManager.setSessionDAO(redisSessionDao);
			return defaultWebSessionManager;
	  }

	  /**
	   * 使用Redis  来存储登录的信息
	   * sessionDao 还需要设置给sessionManager
	   */
	  @Bean
	  public RedisSessionDAO redisSessionDAO(IRedisManager redisManager) {
			RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
//			redisSessionDAO.setKeyPrefix("yangbuyi:his:token:");
			//操作那个redis
			redisSessionDAO.setRedisManager(redisManager);
			// 用户的登录信息保存多久？ 7 天
			redisSessionDAO.setExpire(7 * 24 * 3600);
			return redisSessionDAO;
	  }

	/**
	* @Description: 杨不易个人网址:http://yangbuyi.top
	* 功能描述:  因为分步式项目，所以使用redis去存我们的登陆Session
	* @Author: TeouBle
	* @Date: 2020/8/28 10:02
	*/
	  @Bean
	  public IRedisManager redisManager() {
			//因为RedisManager要操作redis所以必须把Redis的客户端给RedisManager
			RedisManager redisManager = new RedisManager();
			JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
			JedisPool jedisPool = new JedisPool(jedisPoolConfig, redisProperties.getHost(), redisProperties.getPort(), 5000, redisProperties.getPassword());
			redisManager.setJedisPool(jedisPool);
			return redisManager;
	  }

	  /**
	   * spring-session 使用 fastjson 序列化
	   * <p>bean name 要配置成 <strong>springSessionDefaultRedisSerializer</strong>，否则不生效，参考：
	   * {@link RedisHttpSessionConfiguration#setDefaultRedisSerializer(RedisSerializer)}</p>
	   */
	  @Bean
	  @Qualifier("springSessionDefaultRedisSerializer")
	  public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
			return new GenericFastJsonRedisSerializer();
	  }

	  /* 其它设置开始 */

//	  /**
//	  * @功能描述: 注入MybatisPlus 分页插件 PaginationInterceptor
//	  * @Description:
//	  * @Author: TeouBle
//	  * @Date: 2020/9/14 16:40
//	  */
//	  @Bean(name="sessionFactory")
//	  public SqlSessionFactory sessionFactory(PaginationInterceptor paginationInterceptor) throws Exception{
//			SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
//			//构造方法，解决动态数据源循环依赖问题。
//			MybatisConfiguration configuration = new MybatisConfiguration();
//			configuration.addInterceptor(paginationInterceptor);
//			sessionFactoryBean.setConfiguration(configuration);
//			return sessionFactoryBean.getObject();
//	  }


	 /* *//**
	   * cacheManager 缓存 redis实现
	   * 使用的是shiro-redis开源插件
	   *
	   * @return
	   *//*
	  public RedisCacheManager cacheManager() {
			RedisCacheManager redisCacheManager = new RedisCacheManager();
			redisCacheManager.setRedisManager(redisManager());
			return redisCacheManager;
	  }
	  *//**
	   * 限制同一账号登录同时登录人数控制
	   *
	   * @return
	   *//*
	  @Bean
	  public KickoutSessionControlFilter kickoutSessionControlFilter(DefaultWebSessionManager defaultWebSessionManager) {
			KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
			kickoutSessionControlFilter.setCacheManager(cacheManager());
			kickoutSessionControlFilter.setSessionManager(defaultWebSessionManager);
			kickoutSessionControlFilter.setKickoutAfter(false);
			kickoutSessionControlFilter.setMaxSession(1);
			kickoutSessionControlFilter.setKickoutUrl("/auth/kickout");
			return kickoutSessionControlFilter;
	  }
*/

	  /*加入注解的使用，不加入这个注解不生效--开始*/

	  /**
	   * @param securityManager
	   * @return
	   */
	  @Bean
	  public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
			AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
			authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
			return authorizationAttributeSourceAdvisor;
	  }

	  @Bean
	  public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
			DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
			advisorAutoProxyCreator.setProxyTargetClass(true);
			return advisorAutoProxyCreator;
	  }
	  /*加入注解的使用，不加入这个注解不生效--结束*/


}
